#!/usr/bin/python
# -*- coding: UTF-8 -*-
'''
Events package
Created on 21 jun 2011

@author: Joakim Lindskog

Copyright (c) 2011 Joakim Lindskog

This file is part of MyGarden.

    MyGarden is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    MyGarden is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with MyGarden.  If not, see <http://www.gnu.org/licenses/>.
'''
from cocos import layer, text, actions
import util, pyglet
from util import config

class EventBus(object):
    '''
    Event bus for handling events
    '''
    _instance = None
    _initiated = False
    
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(EventBus, cls).__new__(
                                cls, *args, **kwargs)
        return cls._instance
    
    def __init__(self):
        if EventBus._initiated:
            return
        else:
            EventBus._initiated = True
        super(EventBus, self).__init__();
        self.listeners = []
    
    def register_for_events(self, listener):
        '''
        Registers the supplied listener for events from this event bus
        
        A listener must implement the method receive_event(event)
        '''
        if not listener in self.listeners:
            self.listeners.append(listener)
        
    def deregister_for_events(self, listener):
        '''
        Deregisters the supplied listener for events from this event bus
        '''
        if listener in self.listeners:
            self.listeners.remove(listener)
        
    def dispatch_event(self, event):
        '''
        Dispatches an event to all registered listeners
        '''
        for l in self.listeners:
            l.receive_event(event)

class Event(object):
    '''
    Base event class
    '''
    
    def __init__(self, target, x, y):
        self.target = target
        self.x = x
        self.y = y
    
    def __cmp__(self, other):
        # Compare by class
        return cmp(self.__class__, other.__class__)

class IconClickEvent(Event):
    '''
    The user clicked an icon
    '''
    
    def __init__(self, target=None, x=0, y=0, name=None, type=None):
        super(IconClickEvent, self).__init__(target, x, y)
        if not name and target:
            self.name = target.name
        else:
            self.name = name
        self.type = type
            
    def __cmp__(self, other):
        if(hasattr(self, 'name') and self.name
            and hasattr(other, 'name') and other.name):
            # Compare by name
            return cmp(self.name, other.name)
        elif(hasattr(self, 'type') and self.type
             and hasattr(other, 'type') and other.type):
            # Compare by type
            return cmp(self.type, other.type)
        else:
            return -1
        
class BedClickEvent(Event):
    '''
    The user clicked somewhere on a bed
    '''
    
    def __init__(self, target=None, x=0, y=0):
        super(BedClickEvent, self).__init__(target, x, y)
            
    def __cmp__(self, other):
        if(self.__class__ == other.__class__
           and hasattr(self, 'target') and self.target
           and hasattr(other, 'target') and other.target):
            # Compare by target
            return cmp(self.target, other.target)
        else:
            # If no target, compare by class
            return super(BedClickEvent, self).__cmp__(other)
        
class NextStepEvent(Event):
    '''
    The user wanted to proceed to the next step
    '''
    
    def __init__(self, target=None, x=0, y=0):
        super(NextStepEvent, self).__init__(target, x, y)
    
class UseToolEvent(Event):
    '''
    The user used a tool
    '''
    
    def __init__(self, target=None, x=0, y=0, name=None):
        super(UseToolEvent, self).__init__(target, x, y)
        self.name = name
    
    def __cmp__(self, other):
        # Compare by name
        if(self.__class__ == other.__class__
           and hasattr(self, 'name') and self.name
           and hasattr(other, 'name') and other.name):
            return cmp(self.name, other.name)
        else:
            return -1

class RipePlantEvent(Event):
    '''
    A plant became ripe
    '''
    
    def __init__(self, target=None, x=0, y=0):
        super(RipePlantEvent, self).__init__(target, x, y)
        
class PlantGrowingEvent(Event):
    '''
    A plant started to grow
    '''
    
    def __init__(self, target=None, x=0, y=0):
        super(PlantGrowingEvent, self).__init__(target, x, y)
        
class Tutorial(layer.ColorLayer):
    '''
    A tutorial for learning about the basics of the game
    '''
    is_event_handler = True
    
    def __init__(self, game):
        super(Tutorial, self).__init__(230,230,200,240)
        # Set up tutorial window
        self.visible = False
        self.anchor = 0,self.height
        self.text = text.HTMLLabel("",
                               multiline=True,
                               width=400,
                               anchor_x='left',
                               anchor_y='top')
        self.text.x = 10
        self.add(self.text)
        self.game = game
        self.first_plant_growing = False
        self.first_plant_ripe = False
        # Create a list of triggers
        def welcome(event=None):
            self.show("<font size='5'>"+
                      _("tutorial_welcome_title")+"</font><br>"+
                      _("tutorial_welcome_text"))
            self.close_on_click=True
        def open_seeds(event):
            icon = game.gui_layer.menu.seeds_icon
            self.show(_("tutorial_open_seeds"),
                  x=icon.x+icon.width, y=icon.y-10)
        def pick_seed(event):
            window = game.gui_layer.menu.seeds_window
            self.show(_("tutorial_pick_seed"),
                      x=window.x+10, y=window.y+self.height)
        def sow_seed(event):
            self.show(_("tutorial_sow_seed"))
        def growing_info(event):
            # Clear selection
            self.game.reset_selection()
            self.show(_("tutorial_growing_info"))
            self.close_on_click=True
        def click_water(event):
            self.show(_("tutorial_click_water"))
        def use_water(event):
            self.show(_("tutorial_use_water"))
            self.close_on_click=True
        def plant_growing(event):
            def show(dt):
                self.show(_("tutorial_plant_growing"))
                self.close_on_click=True
                self.check_if_growing()
            pyglet.clock.schedule_once(show, 3)
        def click_fertiliser(event):
            self.show(_("tutorial_click_fertiliser"),
                      event.x-20, event.y-30)
        def use_fertiliser(event):
            self.show(_("tutorial_use_fertiliser"))
            self.close_on_click=True
        def weather_info(event):
            def show(dt):
                self.show(_("tutorial_weather_info"))
                self.close_on_click=True
                self.check_if_ripe()
            pyglet.clock.schedule_once(show, 3)
        def plant_ripe(event):
            self.show(_("tutorial_plant_ripe"),
                      event.x, event.y-20)
        def use_shovel(event):
            self.show(_("tutorial_use_shovel"))
            self.close_on_click=True
        def the_end(event):
            self.show(_("tutorial_end"))
            self.close_on_click=True
            
        # List of event triggers for the tutorial
        self.triggers = [Trigger(welcome, NextStepEvent()),
                         Trigger(open_seeds, IconClickEvent(name="seeds")),
                         Trigger(pick_seed, IconClickEvent(type="seeds_window")),
                         Trigger(sow_seed, BedClickEvent()),
                         Trigger(growing_info, NextStepEvent()),
                         Trigger(click_water, IconClickEvent(name="water")),
                         Trigger(use_water, UseToolEvent(name="watering_can")),
                         Trigger(plant_growing, PlantGrowingEvent()),
                         Trigger(click_fertiliser, IconClickEvent(name="fertiliser")),
                         Trigger(use_fertiliser, UseToolEvent(name="grass_fertiliser")),
                         Trigger(weather_info, RipePlantEvent()),
                         Trigger(plant_ripe, IconClickEvent(name="harvest")),
                         Trigger(use_shovel, UseToolEvent(name="harvest_shovel")),
                         Trigger(the_end, NextStepEvent())]
        self.index = 0
        
        # Register for receiving events from the event bus
        EventBus().register_for_events(self)
        
    def show(self, text, x=None, y=None):
        '''
        Shows the tutorial text box
        
        @param text: the text to show
        @type text: string
        
        @param x: the x-coordinate for the text box
        @type x: float/int
        
        @param y: the y-coordinate for the text box
        @type y: float/int
        '''
        def build(text, x, y):
            text = "<font face='Droid Sans' size='3'>"+text+"</font>"
            self.text.element.text = text
            font = self.text.element.document.get_font(None)
            width = util.get_text_width(font, text)+20
            if width < self.text.element.width:
                self.width = width
            else:
                self.width = self.text.element.width+20
            self.height = util.get_text_height(font, text, self.width-20)+20
            self.anchor = 0,self.height
            
            if not x:
                x = config.SCREEN_WIDTH/2-self.width/2
            if not y:
                y = config.SCREEN_HEIGHT/2+self.height/2+150
            x -= self.anchor_x
            y -= self.anchor_y
            if x+self.width > config.SCREEN_WIDTH:
                x = config.SCREEN_WIDTH-self.width
            elif x < 0:
                x = 0
            if y-self.height < 0:
                y = self.height
            elif y > config.SCREEN_HEIGHT:
                y = config.SCREEN_HEIGHT
            self.position = x,y
            
            self.text.y = self.height-10
        
        def add():
            self.game.add(self, 300)
            self.do(actions.Show()+actions.FadeTo(240, 0.5))
        
        if self in self.game.get_children():
            self.do(actions.FadeTo(0, 0.5)+actions.Hide()+actions.CallFunc(lambda: self.kill())+actions.CallFunc(lambda: build(text, x, y))+actions.CallFunc(add))
        else:
            build(text, x, y)
            add()
    
    def check_if_ripe(self):
        '''
        Checks if there has been a ripe event.
        If so, it replays the event.
        '''
        if self.first_plant_ripe:
            # Replay event
            self.receive_event(self.first_plant_ripe)
        
    def check_if_growing(self):
        '''
        Checks if there has been a growing event.
        If so, it replays the event.
        '''
        if self.first_plant_growing:
            # Replay event
            self.receive_event(self.first_plant_growing)
        
    def receive_event(self, event):
        '''
        Receives events
        
        @param event: the event received
        @type event: Event
        '''
        # Check if the event received was the one we are waiting for
        if self.triggers[self.index].event == event:
            self.index += 1
            if len(self.triggers) > self.index:
                # Run the next function
                self.triggers[self.index].function(event)
            else:
                # Tutorial finished
                if self.visible:
                    self.do(actions.FadeTo(0, 0.5)+actions.CallFunc(self.end_tutorial))
                else:
                    self.end_tutorial()
        elif event.__class__ == RipePlantEvent.__class__:
            self.first_plant_ripe = event.target
                
    def end_tutorial(self):
        '''
        Ends the tutorial
        '''
        EventBus().deregister_for_events(self)
        if self in self.game.get_children():
            self.kill()
        
    def start_tutorial(self):
        '''
        Starts the tutorial
        '''
        self.triggers[0].function()
            
    def on_mouse_press(self, x, y, button, modifiers):
        if (x > self.x and x < self.x+self.width
            and y > self.y and y < self.y+self.height
            and self.close_on_click):
            def next_step():
                self.receive_event(NextStepEvent())
                self.close_on_click=False
            self.do(actions.FadeTo(0, 0.5)+actions.Hide()+actions.CallFunc(lambda: self.kill())+actions.CallFunc(next_step))
            return True
        return False
    
    def reset_text(self):
        self.triggers[self.index].function(self.triggers[self.index].event)
        
class Trigger(object):
    '''
    A trigger for events
    '''
    
    def __init__(self, function, event):
        self.function = function
        self.event = event